﻿using Core.Framework.Model.Common;
using Core.Framework.Model.WebSockets;
using Core.Framework.Redis.Queue_Helper;
using Core.Framework.Util;
using Core.Framework.Util.Common;
using Core.IBusiness.ILoggerModule;
using Core.IBusiness.ISocketModule;
using Core.Middleware.WebSockets.TimerJobs;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;

namespace Core.Middleware.WebSockets
{
    /// <summary>
    /// 中间键
    /// </summary>
    public class WebSocketMidWare
    {
        /// <summary>
        /// 请求委托
        /// </summary>
        private readonly RequestDelegate requestDelegate;

        /// <summary>
        /// 日志接口
        /// </summary>
        private ILog iLog = BuildServiceProvider.GetService<ILog>();

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="requestDelegate"></param>
        public WebSocketMidWare(RequestDelegate requestDelegate)
        {
            this.requestDelegate = requestDelegate;

            var job = new TimerJob();
            job.RegisterDisallowConcurrent<InitServiceClinetParameter>(a =>
                a.WithIntervalInSeconds(2).RepeatForever());
        }

        /// <summary>
        /// 执行函数
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public async Task Invoke(HttpContext context)
        {

            // 请求路径
            string requestPath = context.Request.Path;

            // 检测是否是 ws socket 请求
            if (context.WebSockets.IsWebSocketRequest && requestPath.StartsWith("/v1/.ws/"))
            {

                // 获取请求Token
                var token = requestPath.Substring(8, requestPath.Length - 8);

                // 检测是否包含签名信息
                if (string.IsNullOrWhiteSpace(token))
                {
                    await context.Response.WriteAsync("ws link lack of necessary parameters.");
                    return;
                }

                // 拿到会话 session
                WebSocket currentSocket = await context.WebSockets.AcceptWebSocketAsync();

                // 获取客户机信息
                var client = this.GetClientInfoByToken(token);

                // 客户机信息不存在
                if (null == client)
                {
                    // 发送消息
                    await WebSocketApplication.SendAsync(currentSocket, new { type = "userlogin", date = "用户身份信息不存在" });
                    currentSocket.Dispose();
                    return;
                }
                else
                {
                    // 客户机处理
                    this.JoinTheClientGroupAndExecMessage(client, currentSocket);
                }

                // 取消令牌
                CancellationToken cancellationToken = context.RequestAborted;

                // 接收消息处理
                await WebSocketApplication
                    .RecvAsync(
                        currentSocket,
                        cancellationToken,
                        this.WsOutLogin);
            }
            else if (context.Request.Path == "/")
            {
                await context.Response.WriteAsync("hello there !");
            }
            else
            {
                var watch = new Stopwatch();
                watch.Start();

                context.Response.OnStarting(() =>
                {

                    watch.Stop();

                    // 响应时间
                    context.Response.Headers["Request_Time"] = watch.ElapsedMilliseconds.ToString() + "ms";

                    // add log
                    //iLog.Request<WebSocketMidWare>(
                    //    value: new
                    //    {
                    //        Host = context.Request.Host.ToString(),
                    //        Path = context.Request.Path,
                    //        Query = context.Request.QueryString.ToString(),
                    //        Body = new StreamReader(context.Request.Body).ReadToEnd(),
                    //        Time = watch.ElapsedMilliseconds
                    //    }.TryToJson(),
                    //    tag: context.Request.Method
                    //);

                    return Task.CompletedTask;

                });

                await requestDelegate(context);
            }
        }


        #region private

        /// <summary>
        ///  根据签名获取用户信息
        /// </summary>
        /// <param name="token">签名</param>
        /// <returns></returns>
        private ClientInfo GetClientInfoByToken(string token)
        {
            try
            {
                // 根据token获取登陆信息
                var info = RedisQueueHelper.HashGet(RedisQueueHelperParameter.WebSocketByToken, token);

                // 判断是否登陆
                if (!string.IsNullOrWhiteSpace(info))
                {
                    // 反序列化客户机信息
                    var client = ((string)info).TryToEntity<ClientInfo>();

                    // 客户端标识
                    client.ClientToken = token;

                    // 服务器标识
                    client.ServiceToken = Md5Helper.Hash(token + token);

                    return client;
                }

                return null;
            }
            catch (Exception e)
            {
                // projectToken 后面根据 url 来解析
                iLog.Error<WebSocketMidWare>(e.Message, "system");
                return null;
            }

        }


        /// <summary>
        /// 将当前客户机加入组
        /// 并且处理相关消息
        /// </summary>
        private Action<ClientInfo, WebSocket> JoinTheClientGroupAndExecMessage = async (client, webSocket) =>
        {

            // 检测信息是否合法
            if (client != null)
            {

                #region 客户机身份处理

                // 通知客户端登录成功
                await WebSocketApplication.SendAsync(webSocket, new { type = "userlogin", date = 200 });

                // 注册客户机组
                WebSocketApplication.ClientLogin(webSocket, client);

                #endregion

                Thread.Sleep(3000);

                #region 离线消息 Offline

                // 日志接口
                var iLog = BuildServiceProvider.GetService<ILog>();

                // 获取服务
                var service = BuildServiceProvider.GetService<ISocketMessage>();

                // Redis离线消息
                var singleNulls
                    = RedisQueueHelper.GetListPopByLength(RedisQueueHelperParameter.SingleNull, 0, 100, force: true);

                // 查询离线消息
                var list = service.GetOfflineMessagesByMsgKeyAndTemplate(
                    client.User.Subscription,
                    client.Template,
                    client.Project.ProjectToken,
                    new Pagination
                    {
                        page = 1,
                        rows = 200
                    });

                // 构造消息列表
                var offline = new List<string>();


                // 构造消息
                Func<MessageQueue, string> StructureParameter = (messageQueue) =>
                {

                    iLog.Queue<WebSocketMidWare>(new
                    {
                        Client = CoreStartupHelper.GetConfigValue("Service:Title"),
                        TaskUserKey = client.User.Key
                    }.TryToJson(),
                        "offline",
                        messageQueue.ClientInfo.Project.ProjectToken,
                        messageQueue.Message.Token);

                    return new
                    {
                        Token = messageQueue.Message.Token,
                        message = messageQueue.Message.Content,
                        messageParameter = messageQueue.Message.Parameter,

                        userKey = messageQueue.ClientInfo.User.Key,
                        MessageKey = messageQueue.Message.Channel,
                        userParameter = messageQueue.ClientInfo.User.Parameter,
                        sendDateTime = messageQueue.Message.SendDateTime,
                        template = messageQueue.Template.ToLower(),

                        messageType = messageQueue.Message.MessageType.ToString(),

                    }.TryToJson();
                };

                // sql 查询的消息
                foreach (var item in list)
                    offline.Add(StructureParameter(item.MsgContext.TryToEntity<MessageQueue>()));

                // redis 查询的消息
                foreach (var item in singleNulls)
                {
                    var val = ((string)item);

                    if (val.Contains(client.Project.ProjectToken))
                    {
                        var model = val.TryToEntity<MessageQueue>();

                        if (null != model)
                        {
                            if (null != client.Project && model.Template.ToLower() == client.Template.ToLower())
                            {
                                if (
                                    client.Project.ProjectToken == model.ClientInfo.Project.ProjectToken
                                    && client.User.Subscription.Contains(model.Message.Channel)
                                )
                                    offline.Add(StructureParameter(model));
                                else
                                    RedisQueueHelper.ListPush(RedisQueueHelperParameter.SingleNull, item);
                            }
                            else
                                RedisQueueHelper.ListPush(RedisQueueHelperParameter.SingleNull, item);
                        }
                    }
                    else
                        RedisQueueHelper.ListPush(RedisQueueHelperParameter.SingleNull, item);
                }

                // 发送客户端
                await WebSocketApplication.SendAsync(webSocket, new { type = "offline", date = offline });

                #endregion

            }
            else
            {
                await WebSocketApplication.SendAsync(webSocket, new { type = "userlogin", date = "用户身份信息不合法" });
            }


        };

        /// <summary>
        /// 用户登出
        /// </summary>
        private Action<WebSocket> WsOutLogin = (ws) => WebSocketApplication.ClientLoginOut(ws);


        #endregion


    }

}
